/*  
 * Copyright (c) 2025 kkaheng
 * SPDX-License-Identifier: MIT  
 */


package com.kkaheng.grid.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.OverScroller;

public class GridView extends View {

    private float offsetX, offsetY; // 当前行与当前列的偏移量
    private float startX, startY; // 起飞前的偏移量
    private int anchorX, anchorY; // 当前行与当前列(左上角第一个可见单元格)
    private float scaleFactor; // 缩放因子
    private float minScaleFactor = 0.5f, maxScaleFactor = 2; // 缩放因子范围限制
    private Adapter adapter; // 适配器
    private int activeColumn, activeRow; // 当前正在消费事件的行与列
    private boolean scrollable = false; // 是否可滚动(前置条件是表格大小大于控件大小)
    private boolean scalable = false; // 是否允许缩放
    private Rect bounds = new Rect();
    
    private final OverScroller scroller;
    private final GestureDetector gestureDetector;
    private final ScaleGestureDetector scaleDetector;

    public GridView(Context context) {
        this(context, null);
    }

    public GridView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public GridView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
        scroller = new OverScroller(getContext());
        gestureDetector = new GestureDetector(context, new GestureListener());
        scaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    private void init() {
        this.offsetX = 0;
        this.offsetY = 0;
        this.anchorX = 0;
        this.anchorY = 0;
        this.activeColumn = -1;
        this.activeRow = -1;
        this.scaleFactor = 1f;
    }
    
    private boolean lockedX = false, lockedY = false;
    
    /**
     * 边界修正方法，用于确保内容显示在合理范围内，防止出现空白或越界情况
     * 该方法会同时处理横向(X轴)和纵向(Y轴)两个方向的边界检查，确保：
     * 1. 当内容不足时自动锁定到边界
     * 2. 当内容超出时调整锚点和偏移量
     * 3. 当到达边界时停止滚动动画
     */
    private void fixEdge() {
        // 适配器为空则直接返回
        if (adapter == null) return;
        // ==================== 纵向(Y轴)边界检查 ====================
        // 计算需要填充的总高度（控件高度）
        float need = bounds.height();
        // 保存当前Y轴偏移量，用于累计计算
        float save = offsetY;
        
        // s = save;
        // z = 0;
        // n = need;
        
        boolean back = false;
        
        // 向下检查：从锚点行(anchorY)开始向下计算能否填满显示区域
        for (int row = anchorY; row < adapter.getRowCount(); row++) {
            // 获取当前行高度（考虑缩放因子）
            float h = adapter.getHeight(row) * scaleFactor;
            save += h;
            // 如果累计高度已经足够填满显示区域，则停止检查
            if (save >= need) {
                // s = save;
                // z = 1;
                break;
            }
        }
        
        // 如果向下累计高度不足，则向上检查
        if (save < need) {
            // 从锚点行(anchorY-1)开始向上检查
            for (int row = anchorY - 1; row >= 0; row--) {
                back = true;
                float h = adapter.getHeight(row) * scaleFactor;
                save += h;
                // 如果累计高度足够填满显示区域
                if (save >= need) {
                    lockedY = true; // 标记Y轴已锁定
                    anchorY = row; // 调整锚点行
                    // 计算新的偏移量（总高度-累计高度+原始偏移）
                    offsetY = need - save + offsetY;
                    // 处理偏移量过大的情况（超过当前行高度）
                    // s = save;
                    // z = 2;
                    if (offsetY < -h) {
                        anchorY++; // 回退锚点
                        offsetY += h; // 调整偏移
                        // z = 3;
                    }
                    startY -= offsetY;
                    break;
                }
            }
            
            // 如果所有行加起来仍不足显示区域
            if (save - offsetY < need) {
                lockedY = true; // 标记Y轴已锁定
                anchorY = 0; // 重置锚点到首行
                offsetY = 0; // 重置偏移量
                startY = 0; // 重置起始位置
                // s = save;
                // z = -1;
            } else if (!back) {
                lockedY = true;
                // z = -2;
                startY = 0;
                offsetY = need - save + offsetY;
            }
        }
        
        // 处理首行特殊情况
        if (anchorY == 0) {
            // 如果在首行但有正向偏移（顶部出现空白）
            if (offsetY > 0) {
                lockedY = true; // 标记Y轴已锁定
                offsetY = 0; // 重置偏移量
                startY = 0; // 重置起始位置
            }
        }
        
        // ==================== 横向(X轴)边界检查 ====================
        // 重置变量用于X轴计算
        need = bounds.width();
        save = offsetX;
        
        // 向右检查：从锚点列(anchorX)开始向右计算能否填满显示区域
        for (int col = anchorX; col < adapter.getColumnCount(); col++) {
            float w = adapter.getWidth(col) * scaleFactor;
            save += w;
            if (save >= need) break;
        }
        
        // 如果向右累计宽度不足，则向左检查
        if (save < need) {
            back = false;
            for (int col = anchorX - 1; col >= 0; col--) {
                back = true;
                float w = adapter.getWidth(col) * scaleFactor;
                save += w;
                if (save >= need) {
                    lockedX = true;
                    anchorX = col;
                    offsetX = need - save + offsetX;
                    if (offsetX < -w) {
                        anchorX++;
                        offsetX += w;
                    }
                    startX -= offsetX;
                    break;
                }
            }
            
            if (save - offsetX < need) {
                lockedX = true;
                anchorX = 0;
                offsetX = 0;
                startX = 0;
            }  else if (!back) {
                lockedX = true;
                startX = 0;
                offsetX = need - save + offsetX;
            }
        }
        
        // 处理首列特殊情况
        if (anchorX == 0) {
            if (offsetX > 0) {
                lockedX = true;
                offsetX = 0;
                startX = 0;
            }
        }
        
        // 如果两个方向都已锁定边界，则强制停止滚动动画
        if (lockedX && lockedY) {
            if (!scroller.isFinished()) {
                scroller.forceFinished(true);
            }
        }
    }
    
    private void updateAnchor() {
        if (adapter == null) return;
        
        float dx = offsetX / scaleFactor;
        int column = anchorX;
        
        if (dx >= 0) {
            // 右边
            while (column > 0 && column < adapter.getColumnCount() && dx > 0) {
                float w = adapter.getWidth(--column);
                dx -= w;
                startX += w * scaleFactor;
            }
        } else {
            // 左边
            while (column >= 0 && column < adapter.getColumnCount() - 1 && dx <= -adapter.getWidth(column)) {
                float w = adapter.getWidth(column++);
                dx += w;
                startX -= w * scaleFactor;
            }
        }
        
        anchorX = column;
        offsetX = dx * scaleFactor;
        
        float dy = offsetY / scaleFactor;
        int row = anchorY;
        if (dy >= 0) {
            // 下面
            while (row > 0 && row < adapter.getRowCount() && dy > 0) {
                float h = adapter.getHeight(--row);
                dy -= h;
                startY += h * scaleFactor;
            }
        } else {
            // 左边
            while (row >= 0 && row < adapter.getRowCount() - 1 && dy <= -adapter.getHeight(row)) {
                float h = adapter.getHeight(row++);
                dy += h;
                startY -= h * scaleFactor;
            }
        }
        
        anchorY = row;
        offsetY = dy * scaleFactor;
        
        fixEdge();
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = 0;
        int height = 0;

        if (adapter != null) {
            int cols = adapter.getColumnCount();
            int rows = adapter.getRowCount();
            for (int c = 0; c < cols; c++) width += adapter.getWidth(c);
            for (int r = 0; r < rows; r++) height += adapter.getHeight(r);
        }

        width = resolveSize(width, widthMeasureSpec);
        height = resolveSize(height, heightMeasureSpec);
        setMeasuredDimension(width, height);

        this.bounds.set(
                getPaddingLeft(),
                getPaddingTop(),
                getMeasuredWidth() - getPaddingRight(),
                getMeasuredHeight() - getPaddingBottom());
    }
    
    private boolean mIsScrolling;   // 正在拖动
    private boolean mIsScaling;     // 正在缩放
    private boolean isCellConsumingEvent;
    private boolean requestInterceptionEvent; // 请求拦截事件

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (adapter == null) return false;
        
        final int action = event.getActionMasked();
        
        // 先处理ACTION_DOWN事件
        if (action == MotionEvent.ACTION_DOWN) {
            activeColumn = findCellColumn(event.getX());
            activeRow = findCellRow(event.getY());
            
            // 停止当前滚动动画
            if (!scroller.isFinished()) {
                scroller.forceFinished(true);
            }
            
            // 重置操作状态
            mIsScaling = false;
            mIsScrolling = false;
            isCellConsumingEvent = false;
            requestInterceptionEvent = false;
            lockedX = false;
            lockedY = false;
        }
        
        if (!requestInterceptionEvent) {
            scaleDetector.onTouchEvent(event);
            gestureDetector.onTouchEvent(event);
        }
        
        // 判断是否正在进行滚动或缩放操作
        boolean isScrollOrScaleOperation = mIsScrolling || mIsScaling;
        
        // 只有不在滚动/缩放时才考虑单元格事件
        if (!isScrollOrScaleOperation || requestInterceptionEvent) {
            if (activeColumn != -1 && activeRow != -1) {
                // 第一次触摸时检查单元格是否可交互
                if (action == MotionEvent.ACTION_DOWN) {
                    isCellConsumingEvent = adapter.isTouchable(activeColumn, activeRow);
                }
                
                if (isCellConsumingEvent) {
                    MotionEvent cellEvent = toCellEvent(activeColumn, activeRow, event);
                    boolean handled = adapter.dispatchEvent(activeColumn, activeRow, cellEvent);
                    cellEvent.recycle();
                    
                }
            }
        }
        
        // 处理事件结束
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            mIsScrolling = false;
            mIsScaling = false;
            activeColumn = -1;
            activeRow = -1;
            requestInterceptionEvent = false;
        }
        
        return true; // 始终消费事件
    }
    
    private MotionEvent toCellEvent(int col, int row, MotionEvent src) {
        float cellLeft = bounds.left + offsetX;
        for (int c = anchorX; c < col; c++) cellLeft += adapter.getWidth(c) * scaleFactor;
        float cellTop = bounds.top + offsetY;
        for (int r = anchorY; r < row; r++) cellTop += adapter.getHeight(r) * scaleFactor;
    
        MotionEvent dst = MotionEvent.obtain(src);
        dst.offsetLocation(-cellLeft, -cellTop);
        return dst;
    }
    
    // x : 基于View左上角
    private int findCellColumn(float x) {
        x -= bounds.left;
        x -= offsetX;
        x /= scaleFactor;
    
        float acc = 0;
        for (int c = anchorX; c < adapter.getColumnCount(); c++) {
            float w = adapter.getWidth(c);
            if (x >= acc && x < acc + w) return c;
            acc += w;
            if (acc + (offsetX / scaleFactor) > bounds.width() / scaleFactor) break;   // 超界直接停
        }
        return -1;
    }
    
    // y : 基于View左上角
    private int findCellRow(float y) {
        y -= bounds.top;
        y -= offsetY;
        y /= scaleFactor;
    
        float acc = 0;
        for (int r = anchorY; r < adapter.getRowCount(); r++) {
            float h = adapter.getHeight(r);
            if (y >= acc && y < acc + h) return r;
            acc += h;
            if (acc + (offsetY / scaleFactor) > bounds.height() / scaleFactor) break;
        }
        return -1;
    }
    
    
    private class GestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                          float distanceX, float distanceY) {
            if (!scrollable) return false;
            mIsScrolling = true;
            offsetX -= distanceX;
            offsetY -= distanceY;
            updateAnchor();
            invalidate();
            return true;
        }
        @Override public boolean onFling(MotionEvent e1, MotionEvent e2,
                                         float velocityX, float velocityY) {
            if (!scrollable) return false;
            startX = 0;
            startY = 0;
            scroller.fling((int) offsetX, (int) offsetY,
                    (int) velocityX, (int) velocityY,
                    Integer.MIN_VALUE, Integer.MAX_VALUE,
                    Integer.MIN_VALUE, Integer.MAX_VALUE,
                    bounds.width(), bounds.height());
            invalidate();
            return true;
        }
        @Override public boolean onDown(MotionEvent e) {
            if (!scroller.isFinished()) scroller.forceFinished(true);
            mIsScrolling = false;
            return true;
        }
        @Override
        public void onLongPress(MotionEvent e) {
            super.onLongPress(e);
            if (activeColumn == -1 || activeRow == -1) {
                return;
            }
            adapter.onLongClick(activeColumn, activeRow);
        }
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            if (activeColumn == -1 || activeRow == -1) {
                return super.onSingleTapUp(e);
            }
            adapter.onClick(activeColumn, activeRow);
            return true;
        }
    }

    /* —————— 双指缩放 —————— */
    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override public boolean onScale(ScaleGestureDetector detector) {
            if (!scalable) return false;
            float newScale = scaleFactor * detector.getScaleFactor();
            newScale = Math.max(minScaleFactor, Math.min(newScale, maxScaleFactor));

            /* 以焦点为中心缩放 */
            float focusX = detector.getFocusX();
            float focusY = detector.getFocusY();
            offsetX = focusX - (focusX - offsetX) * (newScale / scaleFactor);
            offsetY = focusY - (focusY - offsetY) * (newScale / scaleFactor);

            scaleFactor = newScale;
            updateAnchor();
            invalidate();
            return true;
        }
        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            super.onScaleEnd(detector);
            mIsScaling = false;
        }
        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            mIsScaling = true;
            return super.onScaleBegin(detector);
        }
    }
    
    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {
            if (!lockedX) offsetX = scroller.getCurrX() - startX;
            if (!lockedY) offsetY = scroller.getCurrY() - startY;
            updateAnchor();
            invalidate();
        }
    }
    
    /*
    测试变量
    Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
    float s, n;
    int z;
    */
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (adapter == null) return;
        
        canvas.save();
        canvas.clipRect(bounds); // 裁剪画布
        canvas.translate(bounds.left + offsetX, bounds.top + offsetY);

        float x = 0, y = 0;
        // 从View左上角第一个看得见的单元格开始绘制

        for (int c = anchorX; c < adapter.getColumnCount(); c++) {
            float width = adapter.getWidth(c) * scaleFactor;
            float tx = x + width;
            y = 0;

            for (int r = anchorY; r < adapter.getRowCount(); r++) {
                float height = adapter.getHeight(r) * scaleFactor;
                float ty = y + height;

                canvas.save();
                canvas.translate(x, y);
                canvas.clipRect(0, 0, width, height);

                adapter.onDraw(c, r, canvas, scaleFactor);

                canvas.restore();

                y = ty;
                if (y > bounds.height() - offsetY) {
                    break;
                }
            }

            x = tx;
            if (x > bounds.width() - offsetX) {
                break;
            }
        }

        canvas.restore();
        /*
        p.setTextSize(30);
        p.setColor(0xff404040);
        canvas.drawText("状态 " + z + "，累积 " + s + "，目标 " + n + "，锁X " + lockedX + "，锁Y " + lockedY, 20, 50, p);
        */
    }

    public Adapter getAdapter() {
        return this.adapter;
    }

    public void setAdapter(Adapter adapter) {
        this.adapter = adapter;
        init();
        requestLayout();
    }

    public boolean isScrollable() {
        return this.scrollable;
    }

    public void setScrollable(boolean scrollable) {
        this.scrollable = scrollable;
    }

    public boolean isScalable() {
        return this.scalable;
    }

    public void setScalable(boolean scalable) {
        this.scalable = scalable;
    }
    
    public int getTopColumn() {
    	return anchorX;
    }
    
    public int getTopRow() {
    	return anchorY;
    }
    
    public float getOffsetX() {
    	return offsetX;
    }
    
    public float getOffsetY() {
    	return offsetY;
    }
    
    public float getScaleFactor() {
    	return this.scaleFactor;
    }
    
    public void setScaleFactor(float s) {
    	this.scaleFactor = Math.min(maxScaleFactor, Math.max(minScaleFactor, s));
    }
    
    public float getMinScaleFactor() {
    	return this.minScaleFactor;
    }
    
    public void setMinScaleFactor(float s) {
    	this.minScaleFactor = s;
    }
    
    public float getMaxScaleFactor() {
    	return this.maxScaleFactor;
    }
    
    public void setMaxScaleFactor(float s) {
    	this.maxScaleFactor = s;
    }
    
    public void moveTo(int column, int row, float offsetX, float offsetY) {
    	this.anchorX = column;
        this.anchorY = row;
        this.offsetX = offsetX;
        this.offsetY = offsetY;
        updateAnchor();
    }
    
    public void moveTo(int column, int row) {
    	moveTo(column, row, 0, 0);
    }
    
    // 请求把事件完全交给单元格
    public void requestInterceptionEvent() {
    	this.requestInterceptionEvent = true;
    }

    public abstract static class Adapter {

        // 获取列数
        public abstract int getColumnCount();

        // 获取行数
        public abstract int getRowCount();

        // 获取列宽(px)
        public abstract float getWidth(int column);

        // 获取行高(px)
        public abstract float getHeight(int row);

        // 获取单元格类型(适配器子类用)
        public int getItemType(int column, int row) {
            return 0;
        }

        // 渲染单元格
        public abstract void onDraw(int column, int row, Canvas canvas, float scaleFactor);

        // 事件分发
        public boolean dispatchEvent(int column, int row, MotionEvent event) {
            return onTouchEvent(column, row, event);
        }
        
        public boolean isTouchable(int column, int row) { return false; }
    
        public void onClick(int column, int row) {}
        public void onLongClick(int column, int row) {}
        
        public boolean onTouchEvent(int column, int row, MotionEvent event) {
            return false;
        }
    
    }

}
